﻿namespace Standard
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Windows;
    using System.Windows.Interop;

    public class Screen
    {
        private const int MONITORINFOF_PRIMARY = 0x00000001;

        private IntPtr _hMonitor;

        private Screen(IntPtr hMonitor)
            : this(hMonitor, IntPtr.Zero)
        { }

        private Screen(IntPtr hMonitor, IntPtr hdc)
        {
            Assert.IsNotDefault(hMonitor);

            _hMonitor = hMonitor;

            MONITORINFOEX info = NativeMethods.GetMonitorInfo(hMonitor);


            DeviceName = new string(info.szDevice).Trim('\0');

            using (SafeDC safeDC = (hdc == IntPtr.Zero)
                ? SafeDC.CreateDC(DeviceName)
                : SafeDC.WrapDC(hdc))
            {
                BitsPerPixel = NativeMethods.GetDeviceCaps(safeDC, DeviceCap.BITSPIXEL)
                    * NativeMethods.GetDeviceCaps(safeDC, DeviceCap.PLANES);
            }

            Bounds = DpiHelper.DeviceRectToLogical(new Rect(info.rcMonitor.Left, info.rcMonitor.Top, info.rcMonitor.Width, info.rcMonitor.Height));
            Primary = Utility.IsFlagSet(info.dwFlags, MONITORINFOF_PRIMARY);

            // Defer setting WorkingArea, because it's much more dynamic.
        }

        public static Screen FromWindow(Window window)
        {
            WindowInteropHelper wih = new WindowInteropHelper(window);
            if (IntPtr.Zero == wih.Handle)
            {
                return PrimaryScreen;
            }
            IntPtr hMon = NativeMethods.MonitorFromWindow(wih.Handle, MONITOR.DEFAULTTONEAREST);
            return new Screen(hMon);
        }

        public static Screen FromPoint(Point point)
        {
            Point devPt = DpiHelper.LogicalPixelsToDevice(point);
            POINT pt = new POINT
            {
                // Consider whether there should be rounding heuristics here...
                x = (int)devPt.X,
                y = (int)devPt.Y,
            };

            IntPtr hMon = NativeMethods.MonitorFromPoint(ref pt, MONITOR.DEFAULTTONEAREST);
            return new Screen(hMon);
        }

        public static Screen FromRectangle(Rect rectangle)
        {
            Rect devRect = DpiHelper.LogicalRectToDevice(rectangle);
            // Consider whether there should be rounding heuristics here...
            RECT rc = new RECT((int)devRect.Left, (int)devRect.Top, (int)devRect.Right, (int)devRect.Bottom);

            IntPtr hMon = NativeMethods.MonitorFromRect(ref rc, MONITOR.DEFAULTTONEAREST);
            return new Screen(hMon);
        }

        public static Screen[] AllScreens
        {
            get
            {
                // Could cache this, but would need to listen for changes to user settings.
                List<Screen> screens = new List<Screen>();
                NativeMethods.MonitorEnumProc callbackProc = (monitor, hdc, lprcMonitor, lparam) =>
                    {
                        screens.Add(new Screen(monitor, hdc));
                        return true;
                    };

                using (SafeDC desktop = SafeDC.GetDesktop())
                {
                    NativeMethods.EnumDisplayMonitors(desktop, null, callbackProc, IntPtr.Zero);
                }

                Screen[] ret = new Screen[screens.Count];
                screens.CopyTo(ret);
                return ret;
            }
        }

        public static Screen PrimaryScreen
        {
            get
            {
                return (from screen in AllScreens where screen.Primary select screen).FirstOrDefault();
            }
        }

        public int BitsPerPixel { get; private set; }

        public Rect Bounds { get; private set; }

        public string DeviceName { get; private set; }

        public bool Primary { get; private set; }

        public Rect WorkingArea
        {
            get
            {
                MONITORINFOEX info = NativeMethods.GetMonitorInfo(_hMonitor);
                return DpiHelper.DeviceRectToLogical(new Rect(info.rcWork.Left, info.rcWork.Top, info.rcWork.Width, info.rcWork.Height));
            }
        }

        #region Object overrides

        public override bool Equals(object obj)
        {
            Screen scr = obj as Screen;
            if (scr == null)
            {
                return false;
            }
            return this._hMonitor == scr._hMonitor;
        }

        public override string ToString()
        {
            return string.Format(
                "BitsPerPixel = {0}, Bounds = {1}, DeviceName = {2}, Primary = {3}, WorkingArea = {4}",
                BitsPerPixel,
                Bounds, 
                DeviceName, 
                Primary, 
                WorkingArea);
        }

        public override int GetHashCode()
        {
            return _hMonitor.ToInt32();
        }

        #endregion

        // These are some methods that are trivial to implement
        // outside the class, but are provided on the WinForms version.
        // Since there isn't any optimization being made if they're 
        // implemented internally, I'm not bothering to provide them.
        // They seem like unnecessary bloat.
#if SCREEN_WINFORMS_SURFACE_AREA_PARITY
        public static Rect GetBounds(Window window)
        {
            return Screen.FromWindow(window).Bounds;
        }

        public static Rect GetBounds(Point point)
        {
            return Screen.FromPoint(point).Bounds;
        }

        public static Rect GetBounds(Rect rectangle)
        {
            return Screen.FromRectangle(rectangle).Bounds;
        }

        public static Rect GetWorkingArea(Window window)
        {
            return Screen.FromWindow(window).WorkingArea;
        }

        public static Rect GetWorkingArea(Point point)
        {
            return Screen.FromPoint(point).WorkingArea;
        }

        public static Rect GetWorkingArea(Rect rectangle)
        {
            return Screen.FromRectangle(rectangle).WorkingArea;
        }
#endif

    }

 
}